package org.bdware.doip.endpoint.doipClient;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.log4j.Logger;
import org.bdware.doip.core.crypto.GlobalCertifications;
import org.bdware.doip.core.doipMessage.DoipMessage;
import org.bdware.doip.core.doipMessage.DoipMessageFactory;
import org.bdware.doip.core.exception.DoipConnectException;
import org.bdware.doip.core.model.digitalObject.DigitalObject;
import org.bdware.doip.core.model.metadata.SearchParameter;
import org.bdware.doip.core.utils.GlobalConfigurations;
import org.bdware.doip.endpoint.doipServer.DoipListenerInfo;
import org.bdware.irp.client.GlobalIrpClient;
import org.bdware.irp.client.IrpClient;
import org.bdware.irp.exception.IrpClientException;
import org.bdware.irp.stateinfo.DoStateInfo;
import org.bdware.irp.stateinfo.DoipServiceStateInfo;
import org.bdware.irp.stateinfo.StateInfoBase;

import java.util.HashMap;
import java.util.List;

public class SyncDOAClient {
    Logger logger = Logger.getLogger(SyncDOAClient.class);
    IrpClient irpClient;
    final SyncCallback cb;
    ResponseContainer container;
    static SyncDOAClient instance = null;
    int timeout;


    HashMap<String,DoipClient> clientCache = new HashMap<>();

    public static SyncDOAClient getInstance() throws IrpClientException {
        if(instance ==null){
            instance = new SyncDOAClient(5000);
        }
        return instance;
    }

    public SyncDOAClient(int timeout) throws IrpClientException {
        this.timeout = timeout;
        irpClient = GlobalIrpClient.getGlobalClient();
        container = new ResponseContainer();
        cb = new SyncCallback(container);
    }


    public DoipMessage hello(String repoID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send hello to target DOIP service.");
        doipClient.hello(repoID,cb);
        return waitForResponse();
    }

    public DoipMessage listOperations(String doID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(getTargetRepoByDoID(doID));
        if(doipClient == null) return null;
        logger.info("Send listOperations to target DOIP service.");
        doipClient.listOperations(doID,cb);
        return waitForResponse();
    }

    public DoipMessage listOperations(String doID, String repoID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send listOperations to target DOIP service.");
        doipClient.listOperations(doID,cb);
        return waitForResponse();
    }

    public DoipMessage create(String repoID, DigitalObject digitalObject) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send create to target DOIP service.");
        doipClient.create(repoID,digitalObject,cb);
        return waitForResponse();
    }

    public DoipMessage update(DigitalObject digitalObject) throws IrpClientException {
        if(digitalObject.id == null){
            logger.error("Null digital object ID");
            return null;
        }
        DoipClient doipClient = getDoipClient(getTargetRepoByDoID(digitalObject.id));
        if(doipClient == null) return null;
        logger.info("Send update to target DOIP service.");
        doipClient.update(digitalObject,cb);
        return waitForResponse();
    }

    public DoipMessage update(String repoID, DigitalObject digitalObject) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send update to target DOIP service.");
        doipClient.update(digitalObject,cb);
        return waitForResponse();
    }

    public DoipMessage retrieve(String doID, String elementID, boolean includeElementData) throws IrpClientException {
        DoipClient doipClient = getDoipClient(getTargetRepoByDoID(doID));
        if(doipClient == null) return null;

        logger.info("Send retrieve to target DOIP service.");
        doipClient.retrieve(doID,elementID,includeElementData,cb);
        return waitForResponse();
    }

    public DoipMessage retrieve(String doID, String repoID, String elementID, boolean includeElementData) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send retrieve to target DOIP service.");
        doipClient.retrieve(doID,elementID,includeElementData,cb);
        return waitForResponse();
    }

    public DoipMessage delete(String doID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(getTargetRepoByDoID(doID));
        if(doipClient == null) return null;
        logger.info("Send delete to target DOIP service.");
        doipClient.delete(doID,cb);
        return waitForResponse();
    }

    public DoipMessage delete(String doID, String repoID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        logger.info("Send delete to target DOIP service.");
        doipClient.delete(doID,cb);
        return waitForResponse();
    }

    public DoipMessage search(SearchParameter sp, String registryID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(registryID);
        if(doipClient == null) return null;
        logger.info("Send search to target DOIP service.");
        doipClient.search(registryID, sp,cb);
        return waitForResponse();
    }

    public DoipMessage sendRaw(DoipMessage msg, String repoID) throws IrpClientException {
        DoipClient doipClient = getDoipClient(repoID);
        if(doipClient == null) return null;
        doipClient.sendRawMessage(msg,cb);
        return waitForResponse();
    }
    public String getTargetRepoByDoID(String doID) throws IrpClientException {
        logger.info("Try to resolve doID");
        DoStateInfo doHR = new DoStateInfo(irpClient.resolve(doID));
        logger.info("Digital Object handle record: " + new Gson().toJson(doHR));
        return doHR.getRepository();
    }

    public DoipClient getDoipClient(String repoID) throws IrpClientException {
        DoipClient doipClient;
        if(clientCache.get(repoID) != null){
            doipClient = clientCache.get(repoID);
            if(doipClient.isConnected())
                return doipClient;
            try {
                doipClient.reconnect();
                return doipClient;
            } catch (DoipConnectException e) {
                clientCache.remove(repoID);
                e.printStackTrace();
            }
        }

        logger.info("Try to resolve address of target DOIP service");
        StateInfoBase stateInfoBase = irpClient.resolve(repoID);
        logger.info(new Gson().toJson(stateInfoBase));
        DoipServiceStateInfo repoHR = new DoipServiceStateInfo(stateInfoBase);
        logger.info(new Gson().toJson(repoHR));
        List<DoipListenerInfo> listenerInfos = new Gson().fromJson(repoHR.getListeners(), new TypeToken<List<DoipListenerInfo>>() {
        }.getType());
        logger.info("DOIP service listener information: " + repoHR.getListeners());

        doipClient = new DoipClientImpl();
        doipClient.setRecipientID(repoHR.getOwner());
        for (DoipListenerInfo listener: listenerInfos) {
            doipClient.connect(listener.url);
            if(doipClient.isConnected()){
                logger.info("Choose first available address to connect, url: " + listener.url);
                clientCache.put(repoID,doipClient);
                return doipClient;
            }
        }
        return doipClient;
    }

    DoipMessage waitForResponse(){
        container.response = null;
        synchronized (cb) {
            try {
                cb.wait(timeout);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if(container.response == null) container.response = DoipMessageFactory.createTimeoutResponse(-1);
        return container.response;
    }

    class SyncCallback implements DoipMessageCallback{
        ResponseContainer responseContainer;

        public SyncCallback(ResponseContainer container){
            responseContainer = container;
        }

        @Override
        public synchronized void onResult(DoipMessage msg) {
            responseContainer.response = msg;
            logger.debug("---notify");
            this.notifyAll();
        }
    }

    static class ResponseContainer{
        DoipMessage response;
    }


}
